import {
  Scene,
  PerspectiveCamera,
  Mesh,
  WebGLRenderer,
  CircleGeometry,
  PlaneGeometry,
  Object3D,
  MeshPhongMaterial,
  IcosahedronGeometry,
  CylinderGeometry,
  SphereGeometry,
  MeshPhysicalMaterial,
  PointLight,
} from 'three'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'
import { Reflector } from 'three/examples/jsm/objects/Reflector.js'

class Mirror {
  constructor(ele) {
    if (!(ele instanceof HTMLElement)) {
      throw new Error('第一个参数必须传入dom元素')
    }
    this.groundMirror = null
    this.verticalMirror = null
    this.sphereGroup = null
    this.smallSphere = null

    // 创建场景
    this.scene = new Scene()
    // 创建相机
    this.camera = this.initCamera()
    
    this.addGeoToScene()
    this.addWallToScene()
    this.addLightToScene()

    // 创建渲染器
    this.renderer = this.initRenderer()

    // this.renderer.render( this.scene, this.camera)
    // 将渲染器添加到标签中
    ele.appendChild(this.renderer.domElement)

    this.initControls()

    this.animate()

    window.addEventListener('resize', this.onWindowResize.bind(this), false)
  }

  onWindowResize() {
    const camera = this.camera
    camera.aspect = window.innerWidth / window.innerHeight
    camera.updateProjectionMatrix()
    this.renderer.setSize(window.innerWidth, window.innerHeight)

    this.groundMirror
      .getRenderTarget()
      .setSize(
        window.innerWidth * window.devicePixelRatio,
        window.innerHeight * window.devicePixelRatio
      )
    this.verticalMirror
      .getRenderTarget()
      .setSize(
        window.innerWidth * window.devicePixelRatio,
        window.innerHeight * window.devicePixelRatio
      )
  }

  initControls() {
    const cameraControls = new OrbitControls(
      this.camera,
      this.renderer.domElement
    )
    cameraControls.target.set(0, 40, 0)
    cameraControls.maxDistance = 400
    cameraControls.minDistance = 10
    cameraControls.update()
  }

  initRenderer() {
    const renderer = new WebGLRenderer({ antialias: true })
    renderer.setSize(window.innerWidth, window.innerHeight)
    renderer.setPixelRatio(window.devicePixelRatio)
    return renderer
  }

  initCamera() {
    const camera = new PerspectiveCamera(
      45,
      window.innerWidth / window.innerHeight,
      1,
      500
    )
    camera.position.set(0, 75, 160)
    return camera
  }

  addGeoToScene() {
    this.groundMirror = this.initGroundMirror()
    this.verticalMirror = this.initVerticalMirror()

    this.sphereGroup = new Object3D()
    this.addToGroup(this.sphereGroup)

    this.smallSphere = this.initSmallSphere()
    this.scene.add(
      this.groundMirror,
      this.verticalMirror,
      this.sphereGroup,
      this.smallSphere
    )
  }

  addWallToScene() {
    const planeGeo = new PlaneGeometry(100.1, 100.1)
    // 上面
    const planeTop = new Mesh(
      planeGeo,
      new MeshPhongMaterial({ color: 0xffffff })
    )
    planeTop.position.y = 100
    planeTop.rotateX(Math.PI / 2)
    // 下面
    const planeBott = new Mesh(
      planeGeo,
      new MeshPhongMaterial({ color: 0xffffff })
    )
    planeBott.rotateX(-Math.PI / 2)
    // 前面
    const planeFront = new Mesh(
      planeGeo,
      new MeshPhongMaterial({ color: 0x7f7fff })
    )
    planeFront.position.z = 50
    planeFront.position.y = 50
    planeFront.rotateY(Math.PI)
    // 右面
    const planeRight = new Mesh(
      planeGeo,
      new MeshPhongMaterial({ color: 0x00ff00 })
    )
    planeRight.position.x = 50
    planeRight.position.y = 50
    // 旋转方向: 负值是逆时针,正值是逆时针
    planeRight.rotateY(-Math.PI / 2)

    // 左面
    const planeLeft = new Mesh(
      planeGeo,
      new MeshPhongMaterial({ color: 0xff0000 })
    )
    planeLeft.position.x = -50
    planeLeft.position.y = 50
    planeLeft.rotateY(Math.PI / 2)

    this.scene.add(planeTop, planeBott, planeFront, planeRight, planeLeft)
  }

  // 初始化地面镜子
  initGroundMirror() {
    const circle = new CircleGeometry(40, 74)

    const groundMirror = new Reflector(circle, {
      clipBias: 0.003,
      textureWidth: window.innerWidth * window.devicePixelRatio,
      textureHeight: window.innerHeight * window.devicePixelRatio,
      color: 0x7777,
    })

    groundMirror.position.y = 0.5
    groundMirror.rotateX(-Math.PI / 2)

    return groundMirror
  }

  initVerticalMirror() {
    const geo = new PlaneGeometry(100, 100)
    const verticalMirror = new Reflector(geo, {
      clipBias: 0.003,
      textureHeight: window.innerHeight * window.devicePixelRatio,
      textureWidth: window.innerWidth * window.devicePixelRatio,
      color: 0x889999,
    })
    verticalMirror.position.y = 50
    verticalMirror.position.z = -50
    return verticalMirror
  }

  initSmallSphere() {
    const geometry = new IcosahedronGeometry(5, 0)
    const material = new MeshPhongMaterial({
      color: 0xffffff,
      emissive: 0x333333,
      flatShading: true,
    })
    return new Mesh(geometry, material)
  }

  addToGroup(sphereGroup) {
    const geo1 = new CylinderGeometry(
      0.1,
      15 * Math.cos((Math.PI / 180) * 30),
      0.1,
      24,
      1
    )
    const material = new MeshPhongMaterial({
      color: 0xffffff,
      emissive: 0x444444,
    })
    const sphereCap = new Mesh(geo1, material)
    sphereCap.position.y = -15 * Math.sin((Math.PI / 180) * 30) - 0.05
    sphereCap.rotateX(-Math.PI)

    const geo2 = new SphereGeometry(
      15,
      24,
      24,
      Math.PI / 2,
      Math.PI * 2,
      0,
      (Math.PI / 180) * 120
    )
    const halfSphere = new Mesh(geo2, material)
    halfSphere.add(sphereCap)

    halfSphere.rotateX((-Math.PI / 180) * 135)
    halfSphere.rotateZ((-Math.PI / 180) * 20)
    halfSphere.position.y = 7.5 + 15 * Math.sin((Math.PI / 180) * 30)

    sphereGroup.add(halfSphere)
  }

  addLightToScene() {
    const mainLight = new PointLight(0xcccccc, 1.5, 250)
    mainLight.position.y = 60

    const greenLight = new PointLight(0x00ff00, 0.25, 1000)
    greenLight.position.set(550, 50, 0)

    const redLight = new PointLight(0xff0000, 0.25, 1000)
    redLight.position.set(-550, 50, 0)

    const blueLight = new PointLight(0x7f7fff, 0.25, 1000)
    blueLight.position.set(0, 50, 550)

    this.scene.add(mainLight, greenLight, redLight, blueLight)
  }

  animate() {
    requestAnimationFrame(this.proxy(this.animate, this))
    const timer = Date.now() * 0.01

    this.sphereGroup.rotation.y -= 0.002

    this.smallSphere.position.set(
      Math.cos(timer * 0.1) * 30,
      Math.abs(Math.cos(timer * 0.2)) * 20 + 5,
      Math.sin(timer * 0.1) * 30
    )
    this.smallSphere.rotation.y = Math.PI / 2 - timer * 0.1
    this.smallSphere.rotation.z = timer * 0.8

    this.renderer.render(this.scene, this.camera)
  }

  proxy(fn, context) {
    return () => {
      fn.apply(context)
    }
  }
}

export default Mirror
